{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Разбор названий товаров\n",
    "\n",
    "В этом примере показано, как с помощью Yargy-парсера привести к нормальному виду названия товаров, например:\n",
    "```\n",
    "Продам xbox 360 500 гб\n",
    "{\n",
    "  \"console\": {\n",
    "    \"name\": \"Xbox\",\n",
    "    \"model\": \"360\"\n",
    "  },\n",
    "  \"attributes\": [\n",
    "    {\n",
    "      \"volume\": 500,\n",
    "      \"unit\": \"GB\"\n",
    "    }\n",
    "  ]\n",
    "}\n",
    "\n",
    "Пульт PlayStation3\n",
    "{\n",
    "  \"accessory\": {\n",
    "    \"type\": \"пульт\"\n",
    "  },\n",
    "  \"console\": {\n",
    "    \"name\": \"PlayStation\",\n",
    "    \"model\": \"3\"\n",
    "  }\n",
    "}\n",
    "```\n",
    "\n",
    "Для чего может быть нужна такая технология:\n",
    "1. Семантический поиск. Пользователь пишет в запросе \"плойка\", находятся товары \"PS4 PRO\", \"Sony PlayStation 4 500Gb Slim\" хотя общих подстрок у них нет\n",
    "2. Метчинг товаров. Маркетплейсы типа market.ya.ru, goods.ru собирают информацию о товарах из разных интернет-магазинов. Далеко не всегда магазины знают штрих код товара, приходится как-то понимать, что \"iPhone 4 white\" это то же самое что \"айфон 4 белый\"\n",
    "\n",
    "В примере рассматривается только одна категория \"игровые приставки\" с сайта Авито. Это значительно упрощает задачу. С помощью Yargy-парсера можно написать программу, которая будет понимать и другие категории товаров, но эта задача выходит за рамки данного примера.\n",
    "\n",
    "![](thumb.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Пульт PlayStation3\n",
      "Сонька 3\n",
      "Xbox one\n",
      "Sony PlayStation 4 500Gb Slim\n",
      "Новый жесткий кейс psp\n",
      "Ps3 slim прошитая rebug 4.81 + fifa 18\n",
      "Беспроводной геймпад для Xbox One\n",
      "Sony PlayStation 3 Super Slim 500gb\n",
      "PS 4 white 500g\n",
      "PS3 Sony PlayStation 3 Super Slim 500 GB\n",
      "Sony PlayStation 3 Super Slim\n",
      "PSP\n",
      "Продам xbox 360 500 гб\n",
      "Sony Playstation 3 slim (ps 3 slim) на запчасти\n",
      "SonyPlaystation 4 500gb(белая)\n",
      "PS4 PRO\n",
      "PSP E1008, 32 Гб, прошитая, 73 игры. Вариант 2\n",
      "Sony Playstation 3 Slim, 250 GB, Прошитая\n",
      "PSP - Игровая карманная приставка\n",
      "Sega\n"
     ]
    }
   ],
   "source": [
    "from random import seed, sample\n",
    "\n",
    "\n",
    "def load_lines(path):\n",
    "    with open(path) as file:\n",
    "        for line in file:\n",
    "            yield line.rstrip('\\n')\n",
    "            \n",
    "            \n",
    "lines = list(load_lines('titles.txt'))\n",
    "\n",
    "\n",
    "seed(10)\n",
    "for line in sample(lines, 20):\n",
    "    print(line)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Grammar"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "from yargy import (\n",
    "    Parser,\n",
    "    rule, or_, and_, not_,\n",
    ")\n",
    "from yargy.predicates import (\n",
    "    caseless, type, gram, normalized,\n",
    "    in_, in_caseless, dictionary\n",
    ")\n",
    "from yargy.pipelines import (\n",
    "    caseless_pipeline,\n",
    "    morph_pipeline\n",
    ")\n",
    "from yargy.interpretation import (\n",
    "    fact,\n",
    "    attribute\n",
    ")\n",
    "from yargy import interpretation as interp\n",
    "\n",
    "\n",
    "def test(rule, *tests):\n",
    "    parser = Parser(rule)\n",
    "    for line, etalon in tests:\n",
    "        match = parser.match(line)\n",
    "        assert match, line\n",
    "        guess = match.fact\n",
    "        assert etalon == guess, guess"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Model, vendor"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "Console = fact(\n",
    "    'Console',\n",
    "    ['vendor', 'name', 'model', 'version']\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### PS"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "NAME = morph_pipeline([\n",
    "    'playstation',\n",
    "    'play station',\n",
    "    'ps',\n",
    "    'плейстейшен',\n",
    "    'сонька',\n",
    "    'плестейшен',\n",
    "    'плeйстeшн',\n",
    "    'пс',\n",
    "    'плойка'\n",
    "]).interpretation(\n",
    "    Console.name.const('PlayStation')\n",
    ")\n",
    "\n",
    "MODEL = or_(\n",
    "    in_('1234'),\n",
    "    in_caseless({\n",
    "        'one',\n",
    "        'vr'\n",
    "    })\n",
    ").interpretation(\n",
    "    Console.model.custom(str.upper)\n",
    ")\n",
    "\n",
    "VENDOR = in_caseless([\n",
    "    'sony',\n",
    "    'сони',\n",
    "    'soni'\n",
    "]).interpretation(\n",
    "    Console.vendor.const('Sony')\n",
    ")\n",
    "\n",
    "VERSIONS = {\n",
    "    'super slim': 'SuperSlim',\n",
    "    'superslim': 'SuperSlim',\n",
    "    'slim': 'Slim',\n",
    "    'fat': 'Fat',\n",
    "    'pro': 'PRO',\n",
    "    'vita': 'VITA'\n",
    "}\n",
    "\n",
    "VERSION = caseless_pipeline(VERSIONS).interpretation(\n",
    "    Console.version.normalized().custom(VERSIONS.get)\n",
    ")\n",
    "\n",
    "SEP = in_({'-', '/'})\n",
    "\n",
    "PLAYSTATION = rule(\n",
    "    VENDOR.optional(),\n",
    "    NAME,\n",
    "    SEP.optional(),\n",
    "    MODEL.optional(),\n",
    "    VERSION.optional()\n",
    ").interpretation(\n",
    "    Console\n",
    ")\n",
    "\n",
    "test(\n",
    "    PLAYSTATION,\n",
    "    ['Sony PlayStation 3 Super Slim', Console(vendor='Sony', name='PlayStation', model='3', version='SuperSlim')],\n",
    "    ['Ps vr slim', Console(name='PlayStation', model='VR', version='Slim')]\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "NAME = caseless('psp').interpretation(\n",
    "    Console.name.const('PSP')\n",
    ")\n",
    "\n",
    "MODEL = in_caseless({\n",
    "    'vita',\n",
    "    'slim'\n",
    "}).interpretation(\n",
    "    Console.model.custom(str.capitalize)\n",
    ")\n",
    "\n",
    "PSP = rule(\n",
    "    VENDOR.optional(),\n",
    "    NAME,\n",
    "    SEP.optional(),\n",
    "    MODEL.optional()\n",
    ").interpretation(\n",
    "    Console\n",
    ")\n",
    "\n",
    "test(\n",
    "    PSP,\n",
    "    ['PSP', Console(name='PSP')],\n",
    "    ['Sony Psp vita', Console(vendor='Sony', name='PSP', model='Vita')]\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Xbox"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "NAME = caseless_pipeline([\n",
    "    'x box',\n",
    "    'x-box',\n",
    "    'xbox',\n",
    "    'хbox',  # х русская\n",
    "    'х-бох',\n",
    "    'х-вох',\n",
    "    'хбокс',\n",
    "    'хбох',\n",
    "    'хвох',\n",
    "    'икс бокс',\n",
    "    'иксбокс',\n",
    "]).interpretation(\n",
    "    Console.name.const('Xbox')\n",
    ")\n",
    "\n",
    "MODEL = in_caseless({\n",
    "    '360',\n",
    "    'one',\n",
    "    'original'\n",
    "}).interpretation(\n",
    "    Console.model.custom(str.capitalize)\n",
    ")\n",
    "\n",
    "VERSION = in_caseless({\n",
    "    'slim',\n",
    "    's',\n",
    "    'e',\n",
    "    'fat'\n",
    "}).interpretation(\n",
    "    Console.version.custom(str.capitalize)\n",
    ")\n",
    "\n",
    "XBOX = rule(\n",
    "    NAME,\n",
    "    SEP.optional(),\n",
    "    MODEL.optional(),\n",
    "    VERSION.optional()\n",
    ").interpretation(\n",
    "    Console\n",
    ")\n",
    "\n",
    "test(\n",
    "    XBOX,\n",
    "    ['xbox 360', Console(name='Xbox', model='360')]\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Dendy"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "NAME = in_caseless({\n",
    "    'dendy',\n",
    "    'денди'\n",
    "}).interpretation(\n",
    "    Console.name.const('Dendy')\n",
    ")\n",
    "\n",
    "MODELS = {\n",
    "    'giga drive': 'Giga Drive',\n",
    "    'gigadrive': 'Giga Drive',\n",
    "    'firecore': 'Firecore',\n",
    "    'garfield': 'Garfield',\n",
    "    'junior': 'Junior',\n",
    "    'tomb raider': 'Tomb Raider',\n",
    "    'subor': 'Subor',\n",
    "    'pocket': 'Pocket'\n",
    "}\n",
    "\n",
    "MODEL = caseless_pipeline(MODELS).interpretation(\n",
    "    Console.model.normalized().custom(MODELS.get)\n",
    ")\n",
    "\n",
    "DENDY = rule(\n",
    "    NAME,\n",
    "    SEP.optional(),\n",
    "    MODEL.optional()\n",
    ").interpretation(\n",
    "    Console\n",
    ")\n",
    "\n",
    "test(\n",
    "    DENDY,\n",
    "    ['Dendy Tomb Raider', Console(name='Dendy', model='Tomb Raider')],\n",
    "    ['Dendy Pocket', Console(name='Dendy', model='Pocket')]\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Nintendo"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "VENDOR = in_caseless({\n",
    "    'nintendo',\n",
    "    'нинтендо'\n",
    "}).interpretation(\n",
    "    Console.vendor.const('Nintendo')\n",
    ")\n",
    "\n",
    "NAMES = {\n",
    "    'ds': 'DS',\n",
    "    'switch': 'Switch',\n",
    "    'wii': 'Wii',\n",
    "    'свич': 'Switch',\n",
    "    '3ds': '3DS'\n",
    "}\n",
    "\n",
    "NAME = caseless_pipeline(NAMES).interpretation(\n",
    "    Console.name.normalized().custom(NAMES.get)\n",
    ")\n",
    "\n",
    "VERSION = in_caseless({\n",
    "    'lite',\n",
    "    'mini',\n",
    "    'XL'\n",
    "}).interpretation(\n",
    "    Console.version.custom(str.upper)\n",
    ")\n",
    "\n",
    "NINTENDO = rule(\n",
    "    VENDOR,\n",
    "    SEP.optional(),\n",
    "    NAME.optional(),\n",
    "    VERSION.optional()\n",
    ").interpretation(\n",
    "    Console\n",
    ")\n",
    "\n",
    "\n",
    "test(\n",
    "    NINTENDO,\n",
    "    ['Nintendo Wii', Console(vendor='Nintendo', name='Wii')],\n",
    "    ['Нинтендо Свич', Console(vendor='Nintendo', name='Switch')],\n",
    "    ['Nintendo 3ds XL', Console(vendor='Nintendo', name='3DS', version='XL')],\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Sega"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "VENDOR = in_caseless({\n",
    "    'sega',\n",
    "    'сега'\n",
    "}).interpretation(\n",
    "    Console.vendor.const('Sega')\n",
    ")\n",
    "\n",
    "NAMES = {\n",
    "    'drive': 'Drive',\n",
    "    'megadrive': 'MegaDrive',\n",
    "    'mega drive': 'MegaDrive',\n",
    "    'super drive': 'SuperDrive',\n",
    "    'superdrive': 'SuperDrive',\n",
    "    'dreamcast': 'Dreamcast'\n",
    "}\n",
    "\n",
    "NAME = caseless_pipeline(NAMES).interpretation(\n",
    "    Console.name.normalized().custom(NAMES.get)\n",
    ")\n",
    "\n",
    "SEGA = rule(\n",
    "    VENDOR,\n",
    "    NAME.optional()\n",
    ").interpretation(\n",
    "    Console\n",
    ")\n",
    "\n",
    "test(\n",
    "    SEGA,\n",
    "    ['Sega Super Drive', Console(vendor='Sega', name='SuperDrive')]\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Console"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "CONSOLE = or_(\n",
    "    PLAYSTATION,\n",
    "    PSP,\n",
    "    XBOX,\n",
    "    DENDY,\n",
    "    NINTENDO,\n",
    "    SEGA\n",
    ")\n",
    "\n",
    "PREFIX = morph_pipeline([\n",
    "    'приставка',\n",
    "    'консоль',\n",
    "    'игровая консоль',\n",
    "    'игровая приставка',\n",
    "    'приставка игровая',\n",
    "    'console',\n",
    "])\n",
    "\n",
    "CONSOLE = or_(\n",
    "    PREFIX,\n",
    "    rule(\n",
    "        PREFIX.optional(),\n",
    "        CONSOLE\n",
    "    )\n",
    ").interpretation(\n",
    "    Console\n",
    ")\n",
    "\n",
    "\n",
    "test(\n",
    "    CONSOLE,\n",
    "    ['Игровая приставка Sega Dreamcast', Console(vendor='Sega', name='Dreamcast')],\n",
    "    ['Игровая консоль Sony ps4', Console(vendor='Sony', name='PlayStation', model='4')]\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Games"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "Games = fact(\n",
    "    'Games',\n",
    "    ['number']\n",
    ")\n",
    "\n",
    "\n",
    "INT = type('INT').interpretation(\n",
    "    interp.custom(int)\n",
    ")\n",
    "\n",
    "NUMRS = {\n",
    "    'один': 1,\n",
    "    'два': 2,\n",
    "    'три': 3,\n",
    "    'четыре': 4,\n",
    "    'пять': 5,\n",
    "    'шесть': 6,\n",
    "    'семь': 7,\n",
    "    'восемь': 8,\n",
    "    'девять': 9,\n",
    "    'десять': 10,\n",
    "}\n",
    "\n",
    "NUMR = dictionary(NUMRS).interpretation(\n",
    "    interp.normalized().custom(NUMRS.get)\n",
    ")\n",
    "\n",
    "MANY = dictionary({\n",
    "    'много',\n",
    "    'куча'\n",
    "}).interpretation(\n",
    "    interp.const(float('inf'))\n",
    ")\n",
    "\n",
    "NUMBER = or_(\n",
    "    INT,\n",
    "    NUMR,\n",
    "    MANY\n",
    ").interpretation(\n",
    "    Games.number\n",
    ")\n",
    "\n",
    "WORDS = morph_pipeline([\n",
    "    'игра',\n",
    "    'набор игр',\n",
    "    'диск с игрой',\n",
    "    'диск',\n",
    "    'game',\n",
    "    'games'\n",
    "])\n",
    "\n",
    "GAMES = rule(\n",
    "    NUMBER.optional(),\n",
    "    WORDS\n",
    ").interpretation(\n",
    "    Games\n",
    ")\n",
    "\n",
    "\n",
    "test(\n",
    "    GAMES,\n",
    "    ['много игр', Games(number=float('inf'))],\n",
    "    ['5 дисков', Games(number=5)],\n",
    "    ['два диска с играми', Games(number=2)]\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Accessory"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "Accessory = fact(\n",
    "    'Accessory',\n",
    "    ['number', 'type']\n",
    ")\n",
    "\n",
    "\n",
    "TYPES = {\n",
    "    'camera': 'камера',\n",
    "    'камера': 'камера',\n",
    "\n",
    "    'gamepad': 'джойстик',\n",
    "    'gamepads': 'джойстик',\n",
    "    'joystick': 'джойстик',\n",
    "    'беспроводной геймпад': 'джойстик',\n",
    "    'беспроводной пульт': 'джойстик',\n",
    "    'геймпад джойстик': 'джойстик',\n",
    "    'геймпад': 'джойстик',\n",
    "    'джойстик': 'джойстик',\n",
    "    'джостик': 'джойстик',\n",
    "    'контроллер': 'джойстик',\n",
    "\n",
    "    'kinect': 'kinect',\n",
    "    'kinekt': 'kinect',\n",
    "    'кинект': 'kinect',\n",
    "\n",
    "    'move': 'move',\n",
    "    'мув': 'move',\n",
    "\n",
    "    'кабель': 'кабель',\n",
    "    'композитный кабель': 'кабель',\n",
    "\n",
    "    'чехол': 'чехол',\n",
    "    'защитный чехол': 'чехол',\n",
    "    'кейс': 'чехол',\n",
    "\n",
    "    'dualshock': 'dualshock',\n",
    "    'аксессуар': 'аксессуар',\n",
    "    'блок питания': 'блок питания',\n",
    "    'гарнитура': 'гарнитура',\n",
    "    'педали': 'педали',\n",
    "    'пульт': 'пульт',\n",
    "    'руль': 'руль',\n",
    "    'сменная панель': 'сменная панель',\n",
    "    'карта памяти': 'карта памяти',\n",
    "    'наушники': 'наушники',\n",
    "}\n",
    "\n",
    "TYPE = morph_pipeline(TYPES).interpretation(\n",
    "    Accessory.type.normalized().custom(TYPES.get)\n",
    ")\n",
    "\n",
    "NUMBER = or_(\n",
    "    INT,\n",
    "    NUMR\n",
    ").interpretation(\n",
    "    Accessory.number\n",
    ")\n",
    "\n",
    "ACCESSORY = rule(\n",
    "    NUMBER.optional(),\n",
    "    TYPE\n",
    ").interpretation(\n",
    "    Accessory\n",
    ")\n",
    "\n",
    "test(\n",
    "    ACCESSORY,\n",
    "    ['2 контроллера', Accessory(number=2, type='джойстик')],\n",
    "    ['один мув', Accessory(number=1, type='move')]\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Storage"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "Storage = fact(\n",
    "    'Storage',\n",
    "    ['volume', 'unit']\n",
    ")\n",
    "\n",
    "\n",
    "INT = type('INT')\n",
    "\n",
    "VOLUME = INT.interpretation(\n",
    "    Storage.volume.custom(int)\n",
    ")\n",
    "\n",
    "UNITS = {\n",
    "    'гб': 'GB',\n",
    "    'gb': 'GB',\n",
    "    'g': 'GB',\n",
    "\n",
    "    'тб': 'TB',\n",
    "    'tb': 'TB'\n",
    "}\n",
    "\n",
    "UNIT = in_caseless(UNITS).interpretation(\n",
    "    Storage.unit.normalized().custom(UNITS.get) \n",
    ")\n",
    "\n",
    "STORAGE = rule(\n",
    "    VOLUME,\n",
    "    UNIT\n",
    ").interpretation(\n",
    "    Storage\n",
    ")\n",
    "\n",
    "\n",
    "test(\n",
    "    STORAGE,\n",
    "    ['3гб', Storage(volume=3, unit='GB')],\n",
    "    ['4G', Storage(volume=4, unit='GB')]\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Color"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "Color = fact(\n",
    "    'Color',\n",
    "    ['value']\n",
    ")\n",
    "\n",
    "MAPPING = {\n",
    "    'black': 'чёрный',\n",
    "    'white': 'белый',\n",
    "    'green': 'зелёный',\n",
    "}\n",
    "\n",
    "EN_COLOR = in_caseless(MAPPING).interpretation(\n",
    "    Color.value.normalized().custom(MAPPING.get)\n",
    ")\n",
    "\n",
    "COLOR = dictionary({\n",
    "    'розовый',\n",
    "    'белый',\n",
    "    'чёрный',\n",
    "    'синий',\n",
    "    'фиолетовый',\n",
    "    'красный',\n",
    "    'оранжевый',\n",
    "}).interpretation(\n",
    "    Color.value.normalized()\n",
    ")\n",
    "\n",
    "COLOR = or_(\n",
    "    EN_COLOR,\n",
    "    COLOR\n",
    ").interpretation(\n",
    "    Color\n",
    ")\n",
    "\n",
    "test(\n",
    "    COLOR,\n",
    "    ['черным', Color(value='чёрный')],\n",
    "    ['black', Color(value='чёрный')]\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Patch"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "Patched = fact(\n",
    "    'Patched',\n",
    "    [attribute('patched', False)]\n",
    ")\n",
    "\n",
    "\n",
    "PATCHED = normalized('прошить')\n",
    "\n",
    "PATCHED = PATCHED.interpretation(\n",
    "    Patched.patched.const(True)\n",
    ").interpretation(\n",
    "    Patched\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Console accesory"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "ConsoleAccessory = fact(\n",
    "    'ConsoleAccessory',\n",
    "    ['accessory', 'console']\n",
    ")\n",
    "\n",
    "\n",
    "FOR = in_caseless({\n",
    "    'от',\n",
    "    'для'\n",
    "})\n",
    "\n",
    "CONSOLE_ACCESSORY = rule(\n",
    "    or_(\n",
    "        ACCESSORY,\n",
    "        GAMES\n",
    "    ).interpretation(\n",
    "        ConsoleAccessory.accessory\n",
    "    ),\n",
    "    rule(\n",
    "        FOR.optional(),\n",
    "        CONSOLE.interpretation(\n",
    "            ConsoleAccessory.console\n",
    "        )\n",
    "    ).optional()\n",
    ").interpretation(\n",
    "    ConsoleAccessory\n",
    ")\n",
    "\n",
    "\n",
    "test(\n",
    "    CONSOLE_ACCESSORY,\n",
    "    [\n",
    "        'Блок питание от Xbox360fat',\n",
    "        ConsoleAccessory(\n",
    "            accessory=Accessory(type='блок питания'),\n",
    "            console=Console(name='Xbox', model='360', version='Fat')\n",
    "        )\n",
    "    ],\n",
    "    [\n",
    "        'Наушники для ps4',\n",
    "        ConsoleAccessory(\n",
    "            accessory=Accessory(type='наушники'),\n",
    "            console=Console(name='PlayStation', model='4', version=None)\n",
    "        )\n",
    "    ],\n",
    "    [\n",
    "        '2 диска для PSP',\n",
    "        ConsoleAccessory(\n",
    "            accessory=Games(number=2), \n",
    "            console=Console(name='PSP')\n",
    "        )\n",
    "    ],\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Attributed console"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "AttributedConsole = fact(\n",
    "    'AttributedConsole',\n",
    "    ['console', attribute('attributes').repeatable()]\n",
    ")\n",
    "\n",
    "    \n",
    "PLUS = in_caseless({\n",
    "    '+',\n",
    "    'плюс'\n",
    "})\n",
    "\n",
    "ATTRIBUTE = or_(\n",
    "    GAMES,\n",
    "    STORAGE,\n",
    "    COLOR,\n",
    "    PATCHED,\n",
    "    CONSOLE_ACCESSORY,\n",
    ").interpretation(\n",
    "    AttributedConsole.attributes\n",
    ")\n",
    "\n",
    "ATTRIBUTE = rule(\n",
    "    PLUS.optional(),\n",
    "    ATTRIBUTE\n",
    ")\n",
    "\n",
    "ATTRIBUTED_CONSOLE = rule(\n",
    "    CONSOLE.interpretation(\n",
    "        AttributedConsole.console\n",
    "    ),\n",
    "    ATTRIBUTE.repeatable().optional()\n",
    ").interpretation(\n",
    "    AttributedConsole\n",
    ")\n",
    "\n",
    "\n",
    "test(\n",
    "    ATTRIBUTED_CONSOLE,\n",
    "    [\n",
    "        'Sony PlayStation 3 Super Slim 500 GB + 5 игр прошитая',\n",
    "        AttributedConsole(\n",
    "            console=Console(vendor='Sony', name='PlayStation', model='3', version='SuperSlim'),\n",
    "            attributes=[\n",
    "                Storage(volume=500, unit='GB'),\n",
    "                Games(number=5),\n",
    "                Patched(patched=True)\n",
    "            ]\n",
    "        )\n",
    "    ]\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Title"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "Title = fact(\n",
    "    'Title',\n",
    "    ['item']\n",
    ")\n",
    "\n",
    "\n",
    "TITLE = or_(\n",
    "    ATTRIBUTED_CONSOLE,\n",
    "    CONSOLE_ACCESSORY\n",
    ").interpretation(\n",
    "    Title.item\n",
    ").interpretation(\n",
    "    Title\n",
    ")\n",
    "\n",
    "test(\n",
    "    TITLE,\n",
    "    [\n",
    "        'PS 4 white 500g',\n",
    "        Title(item=AttributedConsole(\n",
    "            console=Console(name='PlayStation', model='4'),\n",
    "            attributes=[\n",
    "                Color(value='белый'),\n",
    "                Storage(volume=500, unit='GB')\n",
    "            ]\n",
    "        ))\n",
    "    ],\n",
    "    [\n",
    "        'Геймпад джойстик для ps 3',\n",
    "        Title(item=ConsoleAccessory(\n",
    "            accessory=Accessory(type='джойстик'),\n",
    "            console=Console(name='PlayStation', model='3')\n",
    "        ))\n",
    "    ]\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Extractor"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Пульт PlayStation3\n",
      "───── ────────────\n",
      "{\n",
      "  \"accessory\": {\n",
      "    \"type\": \"пульт\"\n",
      "  },\n",
      "  \"console\": {\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"3\"\n",
      "  }\n",
      "}\n",
      "Сонька 3\n",
      "────────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"3\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Xbox one\n",
      "────────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"Xbox\",\n",
      "    \"model\": \"One\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Sony PlayStation 4 500Gb Slim\n",
      "────────────────── ─────     \n",
      "{\n",
      "  \"console\": {\n",
      "    \"vendor\": \"Sony\",\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"4\"\n",
      "  },\n",
      "  \"attributes\": [\n",
      "    {\n",
      "      \"volume\": 500,\n",
      "      \"unit\": \"GB\"\n",
      "    }\n",
      "  ]\n",
      "}\n",
      "Новый жесткий кейс psp\n",
      "              ──── ───\n",
      "{\n",
      "  \"accessory\": {\n",
      "    \"type\": \"чехол\"\n",
      "  },\n",
      "  \"console\": {\n",
      "    \"name\": \"PSP\"\n",
      "  }\n",
      "}\n",
      "Ps3 slim прошитая rebug 4.81 + fifa 18\n",
      "──────── ────────            ─        \n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"3\",\n",
      "    \"version\": \"Slim\"\n",
      "  },\n",
      "  \"attributes\": [\n",
      "    {\n",
      "      \"patched\": true\n",
      "    }\n",
      "  ]\n",
      "}\n",
      "Беспроводной геймпад для Xbox One\n",
      "──────────────────── ─── ────────\n",
      "{\n",
      "  \"accessory\": {\n",
      "    \"type\": \"джойстик\"\n",
      "  },\n",
      "  \"console\": {\n",
      "    \"name\": \"Xbox\",\n",
      "    \"model\": \"One\"\n",
      "  }\n",
      "}\n",
      "Sony PlayStation 3 Super Slim 500gb\n",
      "───────────────────────────── ─────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"vendor\": \"Sony\",\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"3\",\n",
      "    \"version\": \"SuperSlim\"\n",
      "  },\n",
      "  \"attributes\": [\n",
      "    {\n",
      "      \"volume\": 500,\n",
      "      \"unit\": \"GB\"\n",
      "    }\n",
      "  ]\n",
      "}\n",
      "PS 4 white 500g\n",
      "──── ───── ────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"4\"\n",
      "  },\n",
      "  \"attributes\": [\n",
      "    {\n",
      "      \"value\": \"белый\"\n",
      "    },\n",
      "    {\n",
      "      \"volume\": 500,\n",
      "      \"unit\": \"GB\"\n",
      "    }\n",
      "  ]\n",
      "}\n",
      "PS3 Sony PlayStation 3 Super Slim 500 GB\n",
      "─── ───────────────────────────── ──────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"3\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Sony PlayStation 3 Super Slim\n",
      "─────────────────────────────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"vendor\": \"Sony\",\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"3\",\n",
      "    \"version\": \"SuperSlim\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "PSP\n",
      "───\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"PSP\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Продам xbox 360 500 гб\n",
      "       ──────── ──────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"Xbox\",\n",
      "    \"model\": \"360\"\n",
      "  },\n",
      "  \"attributes\": [\n",
      "    {\n",
      "      \"volume\": 500,\n",
      "      \"unit\": \"GB\"\n",
      "    }\n",
      "  ]\n",
      "}\n",
      "Sony Playstation 3 slim (ps 3 slim) на запчасти\n",
      "───────────────────────  ─────────             \n",
      "{\n",
      "  \"console\": {\n",
      "    \"vendor\": \"Sony\",\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"3\",\n",
      "    \"version\": \"Slim\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "SonyPlaystation 4 500gb(белая)\n",
      "                  ───── ───── \n",
      "PS4 PRO\n",
      "───────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"4\",\n",
      "    \"version\": \"PRO\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "PSP E1008, 32 Гб, прошитая, 73 игры. Вариант 2\n",
      "───        ─────  ────────  ───────           \n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"PSP\"\n",
      "  },\n",
      "  \"attributes\": [\n",
      "    {\n",
      "      \"volume\": 32,\n",
      "      \"unit\": \"GB\"\n",
      "    },\n",
      "    {\n",
      "      \"patched\": true\n",
      "    },\n",
      "    {\n",
      "      \"number\": 73\n",
      "    }\n",
      "  ]\n",
      "}\n",
      "Sony Playstation 3 Slim, 250 GB, Прошитая\n",
      "───────────────────────  ──────  ────────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"vendor\": \"Sony\",\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"3\",\n",
      "    \"version\": \"Slim\"\n",
      "  },\n",
      "  \"attributes\": [\n",
      "    {\n",
      "      \"volume\": 250,\n",
      "      \"unit\": \"GB\"\n",
      "    },\n",
      "    {\n",
      "      \"patched\": true\n",
      "    }\n",
      "  ]\n",
      "}\n",
      "PSP - Игровая карманная приставка\n",
      "─────                   ─────────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"PSP\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Sega\n",
      "────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"vendor\": \"Sega\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Soni PS3\n",
      "────────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"vendor\": \"Sony\",\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"3\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Sony PSP E1008 2A\n",
      "────────         \n",
      "{\n",
      "  \"console\": {\n",
      "    \"vendor\": \"Sony\",\n",
      "    \"name\": \"PSP\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Dualshock 4 v2 оригиналы\n",
      "─────────               \n",
      "{\n",
      "  \"accessory\": {\n",
      "    \"type\": \"dualshock\"\n",
      "  }\n",
      "}\n",
      "Sony PS4\n",
      "────────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"vendor\": \"Sony\",\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"4\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Xbox Оne 500 gb\n",
      "────     ──────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"Xbox\"\n",
      "  },\n",
      "  \"attributes\": [\n",
      "    {\n",
      "      \"volume\": 500,\n",
      "      \"unit\": \"GB\"\n",
      "    }\n",
      "  ]\n",
      "}\n",
      "Sony playstation 3\n",
      "──────────────────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"vendor\": \"Sony\",\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"3\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Xbox 360\n",
      "────────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"Xbox\",\n",
      "    \"model\": \"360\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Руль и педали\n",
      "────   ──────\n",
      "{\n",
      "  \"accessory\": {\n",
      "    \"type\": \"руль\"\n",
      "  }\n",
      "}\n",
      "PS2\n",
      "───\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"2\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Гадкий я 3\n",
      "Ps4 pro\n",
      "───────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"4\",\n",
      "    \"version\": \"PRO\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Sony PlayStation 4\n",
      "──────────────────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"vendor\": \"Sony\",\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"4\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Xbox 360\n",
      "────────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"Xbox\",\n",
      "    \"model\": \"360\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Ps4\n",
      "───\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"4\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Приставка\n",
      "─────────\n",
      "{\n",
      "  \"console\": {},\n",
      "  \"attributes\": []\n",
      "}\n",
      "Портативная игровая консоль func GP-01\n",
      "            ───────────────           \n",
      "{\n",
      "  \"console\": {},\n",
      "  \"attributes\": []\n",
      "}\n",
      "Sony PlayStation 3 Super Slim\n",
      "─────────────────────────────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"vendor\": \"Sony\",\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"3\",\n",
      "    \"version\": \"SuperSlim\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "PlayStation 3 Super Slim\n",
      "────────────────────────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"3\",\n",
      "    \"version\": \"SuperSlim\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Продаю PS3\n",
      "       ───\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"3\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Игровая приставка Dendy Firecore (новая)\n",
      "────────────────────────────────        \n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"Dendy\",\n",
      "    \"model\": \"Firecore\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Геймпад джойстик для ps 3 новый\n",
      "──────────────── ─── ────      \n",
      "{\n",
      "  \"accessory\": {\n",
      "    \"type\": \"джойстик\"\n",
      "  },\n",
      "  \"console\": {\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"3\"\n",
      "  }\n",
      "}\n",
      "Новые геймпады xbox 360\n",
      "      ──────── ────────\n",
      "{\n",
      "  \"accessory\": {\n",
      "    \"type\": \"джойстик\"\n",
      "  },\n",
      "  \"console\": {\n",
      "    \"name\": \"Xbox\",\n",
      "    \"model\": \"360\"\n",
      "  }\n",
      "}\n",
      "PlayStation 3\n",
      "─────────────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"3\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "PlayStation 3 (Super slim) 320gb\n",
      "─────────────              ─────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"3\"\n",
      "  },\n",
      "  \"attributes\": [\n",
      "    {\n",
      "      \"volume\": 320,\n",
      "      \"unit\": \"GB\"\n",
      "    }\n",
      "  ]\n",
      "}\n",
      "Xbox 360 GTA5 Mortal Combat и еще 50 Игр\n",
      "────────                          ──────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"Xbox\",\n",
      "    \"model\": \"360\"\n",
      "  },\n",
      "  \"attributes\": [\n",
      "    {\n",
      "      \"number\": 50\n",
      "    }\n",
      "  ]\n",
      "}\n",
      "Play Station 4\n",
      "──────────────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"4\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Xbox 360 + 2 беспроводных пульта\n",
      "──────── ─ ─────────────────────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"Xbox\",\n",
      "    \"model\": \"360\"\n",
      "  },\n",
      "  \"attributes\": [\n",
      "    {\n",
      "      \"accessory\": {\n",
      "        \"number\": 2,\n",
      "        \"type\": \"джойстик\"\n",
      "      }\n",
      "    }\n",
      "  ]\n",
      "}\n",
      "Накладки на стики PS4 X-Box\n",
      "                  ─── ─────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"4\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Силиконовый чехол для PSP 3000-3008\n",
      "            ───── ─── ───          \n",
      "{\n",
      "  \"accessory\": {\n",
      "    \"type\": \"чехол\"\n",
      "  },\n",
      "  \"console\": {\n",
      "    \"name\": \"PSP\"\n",
      "  }\n",
      "}\n",
      "Exeq Ray2\n",
      "Игровой джостик Xbox 360\n",
      "        ─────── ────────\n",
      "{\n",
      "  \"accessory\": {\n",
      "    \"type\": \"джойстик\"\n",
      "  },\n",
      "  \"console\": {\n",
      "    \"name\": \"Xbox\",\n",
      "    \"model\": \"360\"\n",
      "  }\n",
      "}\n",
      "Sony Playstation 3 Slim 320G\n",
      "─────────────────────── ────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"vendor\": \"Sony\",\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"3\",\n",
      "    \"version\": \"Slim\"\n",
      "  },\n",
      "  \"attributes\": [\n",
      "    {\n",
      "      \"volume\": 320,\n",
      "      \"unit\": \"GB\"\n",
      "    }\n",
      "  ]\n",
      "}\n",
      "Psp vita\n",
      "────────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"PSP\",\n",
      "    \"model\": \"Vita\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "PSVita материнская плата\n",
      "Xbox 360e 500Gb тмн02\n",
      "───────── ─────      \n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"Xbox\",\n",
      "    \"model\": \"360\",\n",
      "    \"version\": \"E\"\n",
      "  },\n",
      "  \"attributes\": [\n",
      "    {\n",
      "      \"volume\": 500,\n",
      "      \"unit\": \"GB\"\n",
      "    }\n",
      "  ]\n",
      "}\n",
      "Sony Pleystation 2+disk+2jostik\n",
      "                  ─    ─       \n",
      "Игровая приставка Sony PS3 300Gb тб0100006026\n",
      "────────────────────────── ─────             \n",
      "{\n",
      "  \"console\": {\n",
      "    \"vendor\": \"Sony\",\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"3\"\n",
      "  },\n",
      "  \"attributes\": [\n",
      "    {\n",
      "      \"volume\": 300,\n",
      "      \"unit\": \"GB\"\n",
      "    }\n",
      "  ]\n",
      "}\n",
      "Пульт DJ Hero для PS3 + 2 Диска\n",
      "─────         ─── ─── ─ ───────\n",
      "{\n",
      "  \"accessory\": {\n",
      "    \"type\": \"пульт\"\n",
      "  },\n",
      "  \"console\": {\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"3\"\n",
      "  }\n",
      "}\n",
      "PS3 super slim\n",
      "──────────────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"3\",\n",
      "    \"version\": \"SuperSlim\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Sony playstation 3 прошитая куплю\n",
      "────────────────── ────────      \n",
      "{\n",
      "  \"console\": {\n",
      "    \"vendor\": \"Sony\",\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"3\"\n",
      "  },\n",
      "  \"attributes\": [\n",
      "    {\n",
      "      \"patched\": true\n",
      "    }\n",
      "  ]\n",
      "}\n",
      "Sony PlayStation 4\n",
      "──────────────────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"vendor\": \"Sony\",\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"4\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Игровая приставка Dendy Lego Batman 150-in-1\n",
      "───────────────────────                     \n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"Dendy\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Xbox 360 + 2 джостика и 5 игр\n",
      "──────── ─ ──────────   ─────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"Xbox\",\n",
      "    \"model\": \"360\"\n",
      "  },\n",
      "  \"attributes\": [\n",
      "    {\n",
      "      \"accessory\": {\n",
      "        \"number\": 2,\n",
      "        \"type\": \"джойстик\"\n",
      "      }\n",
      "    },\n",
      "    {\n",
      "      \"number\": 5\n",
      "    }\n",
      "  ]\n",
      "}\n",
      "Xbox 360, 250 гб\n",
      "────────  ──────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"Xbox\",\n",
      "    \"model\": \"360\"\n",
      "  },\n",
      "  \"attributes\": [\n",
      "    {\n",
      "      \"volume\": 250,\n",
      "      \"unit\": \"GB\"\n",
      "    }\n",
      "  ]\n",
      "}\n",
      "Sony PlayStation 4 Pro 1tb\n",
      "────────────────────── ───\n",
      "{\n",
      "  \"console\": {\n",
      "    \"vendor\": \"Sony\",\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"4\",\n",
      "    \"version\": \"PRO\"\n",
      "  },\n",
      "  \"attributes\": [\n",
      "    {\n",
      "      \"volume\": 1,\n",
      "      \"unit\": \"TB\"\n",
      "    }\n",
      "  ]\n",
      "}\n",
      "PSP Slim 3008 Прошитая и 3004\n",
      "────────      ────────       \n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"PSP\",\n",
      "    \"model\": \"Slim\"\n",
      "  },\n",
      "  \"attributes\": [\n",
      "    {\n",
      "      \"patched\": true\n",
      "    }\n",
      "  ]\n",
      "}\n",
      "Xbox 360 E 500Gb на гарантии\n",
      "────────── ─────            \n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"Xbox\",\n",
      "    \"model\": \"360\",\n",
      "    \"version\": \"E\"\n",
      "  },\n",
      "  \"attributes\": [\n",
      "    {\n",
      "      \"volume\": 500,\n",
      "      \"unit\": \"GB\"\n",
      "    }\n",
      "  ]\n",
      "}\n",
      "Руль игровой Logiteсh momo\n",
      "────                      \n",
      "{\n",
      "  \"accessory\": {\n",
      "    \"type\": \"руль\"\n",
      "  }\n",
      "}\n",
      "PSP\n",
      "───\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"PSP\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Xbox360\n",
      "───────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"Xbox\",\n",
      "    \"model\": \"360\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Нaбop Мувoв для 3 и 4 плойки\n",
      "      ───   ───       ──────\n",
      "{\n",
      "  \"accessory\": {\n",
      "    \"type\": \"move\"\n",
      "  },\n",
      "  \"console\": {\n",
      "    \"name\": \"PlayStation\"\n",
      "  }\n",
      "}\n",
      "Продам PS4\n",
      "       ───\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"4\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Xbox original\n",
      "─────────────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"Xbox\",\n",
      "    \"model\": \"Original\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Dualshock4\n",
      "───────── \n",
      "{\n",
      "  \"accessory\": {\n",
      "    \"type\": \"dualshock\"\n",
      "  }\n",
      "}\n",
      "Xbox 360\n",
      "────────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"Xbox\",\n",
      "    \"model\": \"360\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Xbox One S 500 Gb + Minecraft (ZQ9-00048)\n",
      "────────── ────── ─                      \n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"Xbox\",\n",
      "    \"model\": \"One\",\n",
      "    \"version\": \"S\"\n",
      "  },\n",
      "  \"attributes\": [\n",
      "    {\n",
      "      \"volume\": 500,\n",
      "      \"unit\": \"GB\"\n",
      "    }\n",
      "  ]\n",
      "}\n",
      "Консоль PS vita\n",
      "───────────────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"version\": \"VITA\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Игровая консоль Sony PS3 500Gb + 1 игра и 1 джойст\n",
      "──────────────────────── ───── ─ ──────           \n",
      "{\n",
      "  \"console\": {\n",
      "    \"vendor\": \"Sony\",\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"3\"\n",
      "  },\n",
      "  \"attributes\": [\n",
      "    {\n",
      "      \"volume\": 500,\n",
      "      \"unit\": \"GB\"\n",
      "    },\n",
      "    {\n",
      "      \"number\": 1\n",
      "    }\n",
      "  ]\n",
      "}\n",
      "PSP 1008\n",
      "───     \n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"PSP\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Беспроводной геймпад для Sony PlayStation 2\n",
      "──────────────────── ─── ──────────────────\n",
      "{\n",
      "  \"accessory\": {\n",
      "    \"type\": \"джойстик\"\n",
      "  },\n",
      "  \"console\": {\n",
      "    \"vendor\": \"Sony\",\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"2\"\n",
      "  }\n",
      "}\n",
      "Настенное крепление для Microsoft Xbox 360 Kinect\n",
      "                    ───           ──────── ──────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"Xbox\"\n",
      "  },\n",
      "  \"attributes\": [\n",
      "    {\n",
      "      \"accessory\": {\n",
      "        \"number\": 360,\n",
      "        \"type\": \"kinect\"\n",
      "      }\n",
      "    }\n",
      "  ]\n",
      "}\n",
      "Xbox 360 500 гб\n",
      "──────── ──────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"Xbox\",\n",
      "    \"model\": \"360\"\n",
      "  },\n",
      "  \"attributes\": [\n",
      "    {\n",
      "      \"volume\": 500,\n",
      "      \"unit\": \"GB\"\n",
      "    }\n",
      "  ]\n",
      "}\n",
      "Новые Виртуальные очки view master\n",
      "Xbox 360 slim c играми\n",
      "─────────────   ──────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"Xbox\",\n",
      "    \"model\": \"360\",\n",
      "    \"version\": \"Slim\"\n",
      "  },\n",
      "  \"attributes\": [\n",
      "    {}\n",
      "  ]\n",
      "}\n",
      "XBox 360 блок питания\n",
      "──────── ────────────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"Xbox\"\n",
      "  },\n",
      "  \"attributes\": [\n",
      "    {\n",
      "      \"accessory\": {\n",
      "        \"number\": 360,\n",
      "        \"type\": \"блок питания\"\n",
      "      }\n",
      "    }\n",
      "  ]\n",
      "}\n",
      "Guitar Hero Live PlayStation 4\n",
      "                 ─────────────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"4\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Nintendo 3ds XL\n",
      "───────────────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"vendor\": \"Nintendo\",\n",
      "    \"name\": \"3DS\",\n",
      "    \"version\": \"XL\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Продам Xbox 360 Fat\n",
      "       ────────────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"Xbox\",\n",
      "    \"model\": \"360\",\n",
      "    \"version\": \"Fat\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Игровая приставка Sony ps3 500gb\n",
      "────────────────────────── ─────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"vendor\": \"Sony\",\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"3\"\n",
      "  },\n",
      "  \"attributes\": [\n",
      "    {\n",
      "      \"volume\": 500,\n",
      "      \"unit\": \"GB\"\n",
      "    }\n",
      "  ]\n",
      "}\n",
      "Sony PSP - E1000\n",
      "──────────      \n",
      "{\n",
      "  \"console\": {\n",
      "    \"vendor\": \"Sony\",\n",
      "    \"name\": \"PSP\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Oculus 2 полный комплект + стелла\n",
      "                         ─       \n",
      "Продам Sony PlayStation 4\n",
      "       ──────────────────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"vendor\": \"Sony\",\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"4\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Игровая приставка Sony ps4\n",
      "──────────────────────────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"vendor\": \"Sony\",\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"4\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Sony PS3\n",
      "────────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"vendor\": \"Sony\",\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"3\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Play station 3 slim 500gb\n",
      "─────────────────── ─────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"3\",\n",
      "    \"version\": \"Slim\"\n",
      "  },\n",
      "  \"attributes\": [\n",
      "    {\n",
      "      \"volume\": 500,\n",
      "      \"unit\": \"GB\"\n",
      "    }\n",
      "  ]\n",
      "}\n",
      "Playstation 3\n",
      "─────────────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"3\"\n",
      "  },\n",
      "  \"attributes\": []\n",
      "}\n",
      "Продам XBox 360 slim 120 gb прошитая\n",
      "       ───────────── ────── ────────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"Xbox\",\n",
      "    \"model\": \"360\",\n",
      "    \"version\": \"Slim\"\n",
      "  },\n",
      "  \"attributes\": [\n",
      "    {\n",
      "      \"volume\": 120,\n",
      "      \"unit\": \"GB\"\n",
      "    },\n",
      "    {\n",
      "      \"patched\": true\n",
      "    }\n",
      "  ]\n",
      "}\n",
      "Ps 2 slim прошитая\n",
      "───────── ────────\n",
      "{\n",
      "  \"console\": {\n",
      "    \"name\": \"PlayStation\",\n",
      "    \"model\": \"2\",\n",
      "    \"version\": \"Slim\"\n",
      "  },\n",
      "  \"attributes\": [\n",
      "    {\n",
      "      \"patched\": true\n",
      "    }\n",
      "  ]\n",
      "}\n",
      "Геймпад Sony Dualshok 3\n",
      "───────                \n",
      "{\n",
      "  \"accessory\": {\n",
      "    \"type\": \"джойстик\"\n",
      "  }\n",
      "}\n",
      "Карта памяти ps vita 32 gb new original\n",
      "──────────── ─────── ─────             \n",
      "{\n",
      "  \"accessory\": {\n",
      "    \"type\": \"карта памяти\"\n",
      "  }\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "import json\n",
    "\n",
    "from ipymarkup import show_span_ascii_markup as show_markup\n",
    "\n",
    "\n",
    "def show_json(data):\n",
    "    print(json.dumps(data, indent=2, ensure_ascii=False))\n",
    "\n",
    "\n",
    "def join_spans(text, spans):\n",
    "    spans = sorted(spans)\n",
    "    return ' '.join(\n",
    "        text[start:stop]\n",
    "        for start, stop in spans\n",
    "    )\n",
    "\n",
    "\n",
    "class Match(object):\n",
    "    def __init__(self, fact, spans):\n",
    "        self.fact = fact\n",
    "        self.spans = spans\n",
    "\n",
    "\n",
    "    \n",
    "DEBUG = or_(\n",
    "    CONSOLE,\n",
    "    ACCESSORY,\n",
    "    rule(FOR),\n",
    "    rule(PLUS),\n",
    "    GAMES,\n",
    "    STORAGE,\n",
    "    COLOR,\n",
    "    PATCHED,\n",
    ")\n",
    "\n",
    "\n",
    "class Extractor(object):\n",
    "    def __init__(self):\n",
    "        self.debug = Parser(DEBUG)\n",
    "        self.parser = Parser(TITLE)\n",
    "\n",
    "    def __call__(self, line):\n",
    "        matches = self.debug.findall(line)\n",
    "        spans = [_.span for _ in matches]\n",
    "        line = join_spans(line, spans)\n",
    "        matches = self.parser.findall(line)\n",
    "        matches = sorted(matches, key=lambda _: _.span)\n",
    "        fact = None\n",
    "        if matches:\n",
    "            fact = matches[0].fact.item\n",
    "        return Match(fact, spans)\n",
    "    \n",
    "    \n",
    "# 6 ошибок, 8 неполных из 100\n",
    "extractor = Extractor()\n",
    "seed(10)\n",
    "for line in sample(lines, 100):\n",
    "    match = extractor(line)\n",
    "    show_markup(line, match.spans)\n",
    "    if match.fact:\n",
    "        show_json(match.fact.as_json)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
